# 采用建造者模式进行整个包的设计，分为五个主要的API
# Q矩阵的生成，题目参数的生成，以及被试参数的生成，模型模拟被试作答，参数估计

import numpy as np
from abc import abstractmethod, ABCMeta
import itertools


class QInterface(metaclass=ABCMeta):
    def __init__(self, i, k, l=2):
        self.i = i
        self.k = k
        self.l = l

    @abstractmethod
    def get(self, *args, **kwargs):
        pass


class StudentInterface(metaclass=ABCMeta):
    def __init__(self, n, k):
        self.n = n
        self.k = k

    @abstractmethod
    def get(self, *args, **kwargs):
        pass


class ItemInterface(metaclass=ABCMeta):
    def __init__(self, i):
        self.i = i

    @abstractmethod
    def get(self, *args, **kwargs):
        pass


class ModelCompute(metaclass=ABCMeta):
    def __init__(self, i, k, n):
        self.i = i
        self.k = k
        self.n = n

    @abstractmethod
    def get(self, *args, **kwargs):
        pass

    @abstractmethod
    def get_possibility(self):
        pass


class ParameterEstimation(metaclass=ABCMeta):
    @abstractmethod
    def get_trace(self):
        pass

    @abstractmethod
    def get_posterior(self):
        pass


class BaseQ(QInterface):
    def __init__(self, i, k, l=2, *args, **kwargs):
        self.i = i
        self.k = k
        self.l = l
        self.I_mat = np.eye(k)
        self.all_p = self.all_pattern()

    def get(self, *args, **kwargs):
        while True:
            Q_element = self.all_p[1:][np.random.randint(0,
                                                         len(self.all_p[1:]),
                                                         size=(self.i - len(self.I_mat)))]
            judge = np.array([Q_element[:, k1] != Q_element[:, k2]
                              for k1 in range(self.k)
                              for k2 in range(self.k)
                              if k1 != k2]).T
            if (judge.sum(axis=0) > 0).all() == True:
                break
        return np.vstack((self.I_mat,
                          Q_element))

    def all_pattern(self):
        all_p = np.array(list(itertools.product(*[range(self.l)], repeat=self.k)))
        return all_p


class BaseStudent(StudentInterface):
    def get(self):
        cache = {
            "att": np.random.binomial(1, 0.5, size=(self.n, self.k))
        }
        return cache


class BaseItem(ItemInterface):

    def get(self, mean_s, mean_g):
        s = np.random.uniform(0, mean_s, size=self.i)
        g = np.random.uniform(0, mean_g, size=self.i)
        cache = {
            "slip": s,
            "guess": g
        }
        return cache
